/*
Copyright (C) 2015 Marien Raat, Pieter Staal

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <lightingGenerator.h>
#include <cmath>
#include <numeric>
#include <iostream>

LightingGenerator::LightingGenerator()
{
    normalmap = 0;
    lightmap = 0;
    firstNormalMap = true;
    firstLightMap = true;
}

void LightingGenerator::generateNormalMap(
    double *heightmap,
    int width,
    int height,
    bool overwriteOldMap)
{
    // Initialize a new map and or delete the old map if needed
    if (firstNormalMap || !overwriteOldMap) {
        normalmap = new Vec3f[width * height];
    } else if (width != currentMapWidth || height != currentMapHeight) {
        delete normalmap;
        normalmap = new Vec3f[width * height];
    }
    firstNormalMap = false;
    currentMapWidth = width;
    currentMapHeight = height;

    // Generate a normal value for every position in the map
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            normalmap[x + y * width] = getNormal(x, y, heightmap);
        }
    }
}

void LightingGenerator::generateLightMap(
    std::vector<Light> lights,
    Vec3f diffuse,
    bool overwriteOldMap)
{
    if (firstNormalMap) return;
    int width = currentMapWidth,
        height = currentMapHeight;
    if (firstLightMap || !overwriteOldMap) {
        lightmap = new Vec3f[width * height];
    } else if (width != previousLightmapWidth ||
             height != previousLightmapHeight) {
        delete lightmap;
        lightmap = new Vec3f[width * height];
    }
    firstLightMap = false;
    previousLightmapWidth = width;
    previousLightmapHeight = height;

    for (int i = 0; i < width * height; i++) lightmap[i] = diffuse;

    for (Light &light : lights) {
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                float distance = sqrt(pow(x - light.position.x, 2) +
                                      pow(y - light.position.y, 2) +
                                      pow(- light.position.z, 2));
                float totalFalloff =
                    light.falloff.x * pow(distance, 1) +
                    light.falloff.y * pow(distance, 2) +
                    light.falloff.z * pow(distance, 3);
                float dotProduct = getDotProduct(x, y, light);
                // std::cout << dotProduct << std::endl;
                lightmap[x + y * width].x += light.color.x *
                    (1.0f - totalFalloff) * dotProduct;
                lightmap[x + y * width].y += light.color.y *
                    (1.0f - totalFalloff) * dotProduct;
                lightmap[x + y * width].z += light.color.z *
                    (1.0f - totalFalloff) * dotProduct;
            }
        }
    }
}

Vec3f LightingGenerator::getNormal(int x, int y, double *heightmap)
{
    // The heightmap value at the point to get the normal from
    double valueAtPoint = heightmap[x + y * currentMapWidth];
    double dx, dy;

    int searchDist = 1;
   
    if (x >= (currentMapWidth - searchDist))
        dx = heightmap[x - searchDist + y * currentMapWidth] - valueAtPoint;
    else
        dx = valueAtPoint - heightmap[x + searchDist + y * currentMapWidth];

    if (y >= (currentMapHeight - searchDist))
        dy = heightmap[x + (y - searchDist) * currentMapWidth] - valueAtPoint;
    else
        dy = valueAtPoint - heightmap[x + (y + searchDist) * currentMapWidth];

    dx *= -200 / searchDist;
    dy *= -200 / searchDist;
    double max = std::max(1.0, std::max(std::abs(dx), std::abs(dy)));

    return {(float) (-dx / max), (float) (-dy / max)
            , (float) (1.0 / max)};
}

float LightingGenerator::getDotProduct(int x, int y, Light light)
{
    std::vector<float> lightDir(3, 0.0),
        normal(3, 0.0);
    lightDir[0] = light.position.x - x;
    lightDir[1] = light.position.y - y;
    lightDir[2] = light.position.z;

    double lmax = std::max(lightDir[0], std::max(lightDir[1]
                                                 , lightDir[2]));
    for (int i = 0; i < 3; i++) lightDir[i] /= lmax;
    
    normal[0] = normalmap[x + y * currentMapWidth].x;
    normal[1] = normalmap[x + y * currentMapWidth].y;
    normal[2] = normalmap[x + y * currentMapWidth].z;

    // std::cout << normal[2] << " " << lightDir[2] << std::endl;

    return std::max(0.0, std::inner_product(lightDir.begin(),
                                            lightDir.end(),
                                            normal.begin(), 0.0));
}
